package backend

import (
	"github.com/gorilla/websocket"
	"time"
	"net/url"
	"sync"
	"proxy"
	"proxy/api"
	"net/http"
	"bytes"
	"io/ioutil"
	"encoding/json"
)

type Conn struct {
	ws      *websocket.Conn
	wsMu    sync.Mutex
	url     *url.URL
	server  *Server
	id      int
	handler ServerHandler
}

func NewConn(h ServerHandler, ws *websocket.Conn) *Conn {
	conn := &Conn{
		handler: h,
		ws:      ws,
		id:      -1,
	}

	ws.SetPongHandler(func (data string) error {
		log.Tracef("conn %d recv pong", conn.id)
		conn.wsMu.Lock()
		err := ws.SetReadDeadline(time.Now().Add(kServerRWTimeout))
		conn.wsMu.Unlock()
		return err
	})

	ws.SetPingHandler(func (data string) error {
		log.Tracef("conn %d recv ping, send pong", conn.id)
		//ws.SetReadDeadline(time.Now().Add(kServerRWTimeout))
		err := ws.WriteControl(websocket.PongMessage, []byte{}, time.Now().Add(kPingPeriod))
		return err
	})

	return conn
}

func (c *Conn) handleRegister() (int, error) {
	msg, err := c.readMessage()
	if err != nil {
		return -1, err
	}

	if msg.Op != OpRegister {
		log.Warningf("conn %d could not register, received '%s'", c.id, OpRegister, msg.Op)
		return -1, proxy.ErrProtocol
	}

	url, err := url.Parse(msg.Url)
	if err != nil {
		log.Warningf("conn %d could not register, url invalid: %s", c.id, err)
		return -1, proxy.ErrProtocol
	}

	id, err := c.handler.OnConnNew(msg)

	if err != nil {
		log.Warningf("conn %d could not register, error: %s", c.id, err)

		rsp := & Message {
			Op:     OpResponse,
			Status: http.StatusForbidden,
			RpcId:  msg.RpcId,
		}

		c.sendMessage(rsp)

		return -1, proxy.ErrProtocol
	}

	c.id = id
	c.url = url

	go c.handlePing()

	log.Infof("conn %d registered", c.id)

	rsp := & Message {
		Op:     OpResponse,
		Status: 0,
		RpcId:  msg.RpcId,
		BackendId: id,
	}

	c.sendMessage(rsp)

	return id, nil
}

func (c *Conn) Close() {
	if err := c.ws.WriteControl(websocket.CloseMessage, []byte{},
	time.Now().Add(kServerRWTimeout)); err != nil {
		log.Warningf("conn %d could not send close message, error: %s", c.id, err)
	}
	return
}

func (c *Conn) handlePing() {
	t := time.NewTicker(kPingPeriod)

	for {
		select {
		case <- t.C:
			log.Tracef("conn %d: %p send ping", c.id, c.ws)
			if err := c.ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(kServerRWTimeout)); err != nil {
				log.Tracef("conn %d: %p send ping error: %s", c.id, c.ws, err)
				return
			}
		}
	}
}

func (c *Conn) handleIncomingMessages() {
	for {
		msg, err := c.readMessage()
		if err != nil {
			return
		}
		if done, err := c.handleMessage(msg); err != nil {
			return
		} else if done {
			return
		}
	}
}

func (c *Conn) readMessage() (*Message, error) {
	msg := &Message{}
	if err := c.ws.SetReadDeadline(time.Now().Add(kServerRWTimeout)); err != nil {
		log.Warningf("conn %d: %p could not set read timeout: %s", c.id,
			c.ws, err)
		return nil, err
	}

	if kind, payload, err := c.ws.ReadMessage(); err != nil {
		log.Warningf("conn %d: %p could not read message: %s", c.id, c.ws, err)
		return nil, err
	} else if kind != websocket.TextMessage {
		log.Warningf("conn %d: %p could not handle non text message", c.id,
			c.ws)
		return nil, proxy.ErrProtocol
	} else {
		log.Debugf("conn %d: %p received '%s'", c.id, c.ws, payload)
		if err := json.Unmarshal(payload, &msg); err != nil {
			log.Warningf("conn %d could not decode received msg: %s", c.id, err)
			return nil, proxy.ErrProtocol
		}
	}
	return msg, nil
}

func (c *Conn) handleMessage(msg *Message) (bool, error) {
	switch msg.Op {
	case OpUnRegister:
		c.handleUnRegister(msg)
		return true, nil
	case OpKeepAlive:
		c.handleKeepAlive(msg)
		return false, nil
	case OpRtcDestroy:
		c.handler.OnRtcDestroy(msg)
		return false, nil
	}

	log.Errorf("conn %d could not handle message '%s'", c.id, msg.Op)
	return true, proxy.ErrProtocol
}

func (c *Conn) handleUnRegister(msg *Message) {
	log.Warningf("conn %d connection un-register", c.id)
	rsp := &Message{
		Op:    "rsp",
		RpcId: msg.RpcId,
	}
	c.sendMessage(rsp)
}

func (c *Conn) handleKeepAlive(msg *Message) {
	c.handler.OnKeepAlive(c.id, msg)
}

func (c *Conn) forwardReq(req *api.Request) (*api.Response, error) {
	reqBuf := req.Encode()

	httpRsp, err := http.Post(c.url.String(), kApiContentTypeJson, bytes.NewReader(reqBuf))
	if err != nil {
		log.Warningf("[%s:%s] forward failed, could not send http request: %s", req.TsxId, req.Op, err)
		return nil, proxy.ErrNetwork
	}

	rspBuf, err := ioutil.ReadAll(httpRsp.Body)
	httpRsp.Body.Close()
	if err != nil {
		log.Warningf("[%s:%s] forward failed, could not read response: %s", req.TsxId, req.Op, err)
		return nil, proxy.ErrNetwork
	}

	if httpRsp.StatusCode != http.StatusOK {
		log.Warningf("[%s:%s] forward failed, response http status %s", req.TsxId, req.Op, httpRsp.Status)
		return nil, proxy.ErrProtocol
	}

	log.Infof("[%s:%s] forwarded to %s via conn %d, got response", req.TsxId, req.Op, c.url.String(), c.id)

	rsp, err := api.ParseResponse(rspBuf)
	if err != nil {
		log.Warningf("[%s:%s] could not parse response, error: %s", req.TsxId, req.Op, err)
		return nil, proxy.ErrProtocol
	}

	return rsp, nil
}

func (c *Conn) sendMessage(msg *Message) error {

	c.wsMu.Lock()

	if err := c.ws.SetWriteDeadline(time.Now().Add(kServerRWTimeout)); err != nil {
		c.wsMu.Unlock()
		log.Warningf("conn %d could not set write deadline: %s", c.id, err)
		return proxy.ErrNetwork
	}

	//text, err := json.MarshalIndent(msg, "", "\t")
	text, err := json.Marshal(msg)
	if err != nil {
		log.Panicf("conn %d could not encode msg: %s", err)
	}

	log.Debugf("conn %d send %s", c.id, text)

	if err := c.ws.WriteMessage(websocket.TextMessage, text); err != nil {
		c.wsMu.Unlock()
		log.Warningf("conn %d could not send message: %s", c.id, err)
		return proxy.ErrNetwork
	}

	c.wsMu.Unlock()

	return nil
}
